﻿using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.IO;

namespace System
{
	public partial struct Line3D
	{
		public V3D A;
		public V3D B;

		public V3D InterPoint(double n)
		{
			return V3D.Interpolate(A, B, n);
		}
		public double Inter(V3D P)
		{
			P = NearestPoint(P);
			double L1 = (P - A).Length / Length;
			return L1 * Math.Sign((P - A) * Dir);
		}
		public bool Valid
		{
			get
			{
				return A.Valid && B.Valid;
			}
			set
			{
				if (!value)
				{
					A = V3D.Null;
					B = V3D.Null;
				}
			}
		}
		public V3D Dir
		{
			get
			{
				return (B - A).Unit;
			}
		}
		public double Length
		{
			get
			{
				return (B - A).Length;
			}
		}
		public Line3D(V3D a, V3D b)
		{
			A = a;
			B = b;
			Valid = a.Valid && b.Valid;
		}
		public Line3D(double ax, double ay, double az, double bx, double by, double bz)
		{
			A = new V3D(ax, ay, az);
			B = new V3D(bx, by, bz);
		}

		public override String ToString()
		{
			if (Valid)
				return A.ToString(3, 1) + " - " + B.ToString(3, 1);
			else
				return "<<Invalid>>";
		}
		public static Line3D Null
		{
			get
			{
				return new Line3D(V3D.Null, V3D.Null);
			}
		}

		//Geometry
		public bool InBeam(V3D Point)
		{
			return (Point - A) * Dir >= 0 && (Point - B) * Dir <= 0;
		}
		public double DistanceInf(V3D Point)
		{
			return ((A - Point) % (B - Point)).Length / Length;
		}
		public double DistanceSeg(V3D Point)
		{
			if (InBeam(Point))
				return DistanceInf(Point);
			else if ((Point - A) * Dir < 0)
				return (Point - A).Length;
			else
				return (Point - B).Length;
		}
		public bool PointIn(V3D P)
		{
			return P.Valid && Dir * (P - A) >= 0 && (-Dir * (P - B) >= 0);
		}

		//Intersection Points
		public static V3D operator *(Line3D L1, Line3D L2)
		{
			return L1.NearestLine(L2);
		}
		public static V3D operator *(Line3D L, Plane3D P)
		{
			if (L.Dir * P.Normal == 0)
				return V3D.Null;
			double l1 = (L.B - L.A).ParallelPart(P.Normal).Length;
			double l2 = (P.Origin - L.A).ParallelPart(P.Normal).Length;
			return L.A + (L.B - L.A) * l2 / l1;
		}

		//Distance from Point
		public V3D NearestPoint(V3D p)
		{
			if ((A - p).Length == 0)
				return V3D.Black;
			return p + (A - p).CrossPart(Dir);
		}
		public V3D DistanceFromPoint(V3D p)
		{
			return NearestPoint(p) - p;
		}
		public V3D NearestPoint_Seg(V3D p)
		{
			V3D Res = NearestPoint(p);
			if (Res.Valid)
			{
				if ((Res - A) * Dir < 0)
					return A;
				else
					if ((B - Res) * Dir < 0)
						return B;
			}
			return Res;
		}
		public V3D DistanceFromPoint_Seg(V3D p)
		{
			return NearestPoint_Seg(p) - p;
		}

		//Distance from Line
		public V3D NearestLine(Line3D L)
		{
			if (!(Valid && L.Valid))
				return V3D.Null;
			else
			{
				V3D D1 = Dir;
				V3D D2 = L.Dir;
				V3D N = (Dir % L.Dir).Unit;
				V3D T = (L.A - A);
				V3D Dist = T.ParallelPart(N);
				V3D TC = T - Dist;
				V3D A2C = A + TC;
				double alpha = V3D.Angle(TC, D1);
				double beta = V3D.Angle(D1, -D2);
				V3D Res = A2C + D2 * (TC.Length * Math.Sin(alpha) / Math.Sin(beta)) * Math.Sign(N * (TC % D1));
				return Res;
			}
		}
		public Line3D DistanceFromLine(Line3D L)
		{
			if (!(Valid && L.Valid))
				return Line3D.Null;
			else
			{
				return new Line3D(this * L, L * this);
			}
		}

		public bool OnLine(V3D V)
		{
			return Math.Abs(Dir.Unit * (V - A).Unit) >= 1;//-1e-2000000;
		}
		public bool CoLinear(Line3D L)
		{
			return
				//(this.Dir % L.Dir == V3D.Black) ||
			Math.Abs(Dir * L.Dir / (Length * L.Length)) >= 1 - 1e-10;
		}

		public Line3D Extend(Line3D L)
		{
			V3D P = this * L;
			if (!P.Valid)
			{
				Console.WriteLine("Invalid!");
				return Line3D.Null;
			}
			double N = Inter(P);
			Console.WriteLine(N);
			return Line3D.Null;
		}
	}
	public partial struct V3D
	{
		public static V3D CircleCenter(V3D P1, V3D P2, V3D P3)
		{
			V3D N = ((P2 - P1) % (P3 - P1)).Unit;

			V3D A1 = (P1 + P2) / 2;
			V3D A2 = A1 + (P2 - P1).Rot(N, 90);
			V3D B1 = (P1 + P3) / 2;
			V3D B2 = B1 + (P3 - P1).Rot(N, -90);
			V3D Res = new Line3D(A1, A2) * new Line3D(B1, B2);
			return Res;
		}
	}

	public class Polygon3D
	{
		public List<PolyLine3D> Lines = new List<PolyLine3D>();
		public List<bool> Dir = new List<bool>();

		public Polygon3D()
		{
		}

		public void AddLine(PolyLine3D L, bool d)
		{
			Lines.Add(L);
			Dir.Add(d);
		}
		public V3D LastPoint
		{
			get
			{
				List<V3D> lastline = Lines[Lines.Count - 1].Points;
				return lastline[Dir[Dir.Count - 1] ? 0 : lastline.Count - 1];
			}
		}
		public V3D FirstPoint
		{
			get
			{
				List<V3D> firstline = Lines[0].Points;
				return firstline[Dir[0] ? firstline.Count - 1 : 0];
			}
		}
	}
	public class Polygon3G
	{
		public List<PolyLine3G> Lines = new List<PolyLine3G>();
		public V3G[] Points;
		List<bool> Dir = new List<bool>();
		public void Generate()
		{

		}

		public Polygon3G(params PolyLine3G[] lines)
		{
			Lines.AddRange(lines);
		}

		public void AddLine(PolyLine3G L, bool d)
		{
			Lines.Add(L);
		}
		public V3G LastPoint
		{
			get
			{
				List<V3G> lastline = Lines[Lines.Count - 1].Points;
				return lastline[Dir[Dir.Count - 1] ? 0 : lastline.Count - 1];
			}
		}
		public V3G FirstPoint
		{
			get
			{
				List<V3G> firstline = Lines[0].Points;
				return firstline[Dir[0] ? firstline.Count - 1 : 0];
			}
		}

		public static void Arrange(Polygon3G p)
		{

		}
	}

	public class PolyLine3D
	{
		public void Write(bool Dir)
		{
			int D = Dir ? -1 : 1;
			int N = Dir ? Points.Count - 1 : 0;
			int E = Dir ? -1 : Points.Count;
			for (int i = N; i != E; i += D)
				Console.WriteLine(Points[i]);
		}
		public List<V3D> Points = new List<V3D>();
		public PolyLine3D(params V3D[] points)
		{
			Points.AddRange(points);
		}
		public void AddPoint(V3D P)
		{
			Points.Add(P);
		}
		public double Length
		{
			get
			{
				double res = 0;
				for (int i = 0; i < Points.Count - 1; i++)
					res += (Points[i] - Points[i + 1]).Length;
				return res;
			}
		}
		public V3D LastPoint
		{
			get
			{
				return Points[Points.Count - 1];
			}
		}
		public V3D FirstPoint
		{
			get
			{
				return Points[0];
			}
		}
		public static Polygon3D[] GetPolyLineCircuits(params PolyLine3D[] lines)
		{
			List<Polygon3D> Results = new List<Polygon3D>();
			List<PolyLine3D> stack = new List<PolyLine3D>();
			stack.AddRange(lines);
			while (stack.Count > 0)
			{
				Polygon3D current = new Polygon3D();
				current.AddLine(stack[0], false);
				stack.RemoveAt(0);

				bool finished = false;
				while (!finished)
				{
					if (current.FirstPoint == current.LastPoint)
					{
						finished = true;
					}
					else
					{
						bool foundnext = false;
						for (int i = 0; !foundnext && i < stack.Count; i++)
						{
							if (stack[i].FirstPoint == current.LastPoint)
							{
								foundnext = true;
								current.AddLine(stack[i], false);
								stack.RemoveAt(i);
							}
							else if (stack[i].LastPoint == current.LastPoint)
							{
								foundnext = true;
								current.AddLine(stack[i], true);
								stack.RemoveAt(i);
							}
						}
						if (!foundnext)
						{
							if (stack.Count == 0)
								throw new Exception("Polygon cannot be closed");
							else
								throw new Exception("Lines cannot be fully bound together");
						}
					}
				}
				Results.Add(current);
			}
			return Results.ToArray();
		}

		public PolyLine3D Parallel(double width)
		{
			return Parallel(width, V3D.Green);
		}
		public PolyLine3D Parallel(double width, V3D Up)
		{
			PolyLine3D Res = new PolyLine3D(null);
			Line3D[] segments=new Line3D[Points.Count-1];
			for (int i = 0; i < Points.Count - 1; i++)
			{
				segments[i] = new Line3D(Points[i], Points[i + 1]);
				segments[i].A = segments[i].A + (segments[i].Dir % Up).Unit * width;
				segments[i].B = segments[i].B + (segments[i].Dir % Up).Unit * width;
			}
			return Res;
		}
	}
	public class PolyLine3G
	{
		public void Write(bool Dir)
		{
			int D = Dir ? -1 : 1;
			int N = Dir ? Points.Count - 1 : 0;
			int E = Dir ? -1 : Points.Count;
			for (int i = N; i != E; i += D)
				Console.WriteLine(Points[i]);
		}
		public List<V3G> Points = new List<V3G>();
		public PolyLine3G(params V3G[] points)
		{
			Points.AddRange(points);
		}
		public void AddPoint(V3G P)
		{
			Points.Add(P);
		}
		public double Length
		{
			get
			{
				double res = 0;
				for (int i = 0; i < Points.Count - 1; i++)
					res += (Points[i] - Points[i + 1]).Length;
				return res;
			}
		}
		public V3G LastPoint
		{
			get
			{
				return Points[Points.Count - 1];
			}
		}
		public V3G FirstPoint
		{
			get
			{
				return Points[0];
			}
		}
		public static Polygon3G[] GetPolyLineCircuits(params PolyLine3G[] lines)
		{
			List<Polygon3G> Results = new List<Polygon3G>();
			List<PolyLine3G> stack = new List<PolyLine3G>();
			stack.AddRange(lines);
			while (stack.Count > 0)
			{
				Polygon3G current = new Polygon3G();
				current.AddLine(stack[0], false);
				stack.RemoveAt(0);

				bool finished = false;
				while (!finished)
				{
					if (current.FirstPoint == current.LastPoint)
					{
						finished = true;
					}
					else
					{
						bool foundnext = false;
						for (int i = 0; !foundnext && i < stack.Count; i++)
						{
							if (stack[i].FirstPoint == current.LastPoint)
							{
								foundnext = true;
								current.AddLine(stack[i], false);
								stack.RemoveAt(i);
							}
							else if (stack[i].LastPoint == current.LastPoint)
							{
								foundnext = true;
								current.AddLine(stack[i], true);
								stack.RemoveAt(i);
								}
						}
						if (!foundnext)
						{
							if (stack.Count == 0)
								throw new Exception("Polygon cannot be closed");
							else
								throw new Exception("Lines cannot be fully bound together");
						}
					}
				}
				Results.Add(current);
			}
			return Results.ToArray();
		}
	}
}